package com.gitee.cui.security.crypto;

import com.gitee.cui.config.SystemConfig;
import com.gitee.cui.util.IOUtil;
import lombok.extern.slf4j.Slf4j;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.crypto.Cipher;
import java.io.*;
import java.security.*;

/**
 * 非对称加密算法，通过一对公钥和私钥进行加密解密，通过公钥加密，只能通过私钥解密，通过私钥加密，只能通过公钥解密
 * @author cuiqingqiang
 * @version 1.0
 * @date 2021/11/4 14:23
 **/
@Slf4j
public class RSAEncrypt {

    /**
     * 定义加密算法
     */
    private static final String RSA = "RSA";

    /**
     * 初始化密钥长度
     */
    private static final int SIZE = 1024;

    /**
     * 公钥存放文件
     */
    private static final String PUBLIC_KEY_FILE = SystemConfig.getKeystoreDir() + "/PublicKey";

    /**
     * 私钥存放文件
     */
    private static final String PRIVATE_KEY_FILE = SystemConfig.getKeystoreDir() + "/PrivateKey";

    public static void main(String[] args) throws Exception {
        // 生成密钥对
        generateKeyPair();

        // 待加密内容
        String text = "测试加密内容";

        // 执行加密操作
        String secret = encrypt(text);
        log.info("原始内容：{}，使用公钥加密后：{}", text, secret);

        // 执行解密操作
        String result = decrypt(secret);
        log.info("使用私钥解密后：{}", result);
    }

    /**
     * 解密
     * @param secret 待解密内容
     * @return 解密后内容
     */
    private static String decrypt(String secret) throws Exception {
        // 从文件加载私钥
        PrivateKey privateKey = loadPrivateKey();

        // 初始化Cipher并初始化
        Cipher cipher = Cipher.getInstance(RSA);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);

        // 先用BASE64Decoder进行解码，然后再用私钥解密
        BASE64Decoder base64Decoder = new BASE64Decoder();
        byte[] bytes = base64Decoder.decodeBuffer(secret);
        byte[] result = cipher.doFinal(bytes);
        return new String(result);
    }

    /**
     * 从文件加载私钥
     * @return 私钥内容
     */
    public static PrivateKey loadPrivateKey() throws Exception {
        PrivateKey privateKey = null;
        ObjectInputStream inputStream = null;
        try {
            log.info("PRIVATE_KEY_FILE 读取：{}", PRIVATE_KEY_FILE);
            inputStream = new ObjectInputStream(new FileInputStream(PRIVATE_KEY_FILE));
            privateKey = (PrivateKey) inputStream.readObject();
        } catch (Exception e) {
            throw e;
        } finally {
            IOUtil.closeQuietly(inputStream);
        }
        return privateKey;
    }

    /**
     * 加密
     * @param text 待加密内容
     * @return 加密后内容
     */
    private static String encrypt(String text) throws Exception {
        // 加载公钥
        PublicKey publicKey = loadPublicKey();
        // 构建Cipher对象并完成加密操作
        Cipher cipher = Cipher.getInstance(RSA);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] bytes = text.getBytes();
        byte[] result = cipher.doFinal(bytes);
        BASE64Encoder base64Encoder = new BASE64Encoder();
        return base64Encoder.encode(result);
    }

    /**
     * 从本地文件中加载公钥
     * @return 公钥内容
     */
    public static PublicKey loadPublicKey() throws Exception {
        PublicKey publicKey = null;
        ObjectInputStream inputStream = null;
        try {
            log.info("PUBLIC_KEY_FILE 读取:{}", PUBLIC_KEY_FILE);
            inputStream = new ObjectInputStream(new FileInputStream(PUBLIC_KEY_FILE));
            publicKey = (PublicKey) inputStream.readObject();
        } catch (Exception e) {
            throw e;
        } finally {
            IOUtil.closeQuietly(inputStream);
        }
        return publicKey;
    }

    /**
     * 生成密钥对
     */
    private static void generateKeyPair() throws Exception {
        // 为RSA算法创建一个密钥对生成器
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA);
        // 初始化密钥长度
        keyPairGenerator.initialize(SIZE);
        // 生成密钥对
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        // 生成公钥
        PublicKey publicKey = keyPair.getPublic();
        // 生成私钥
        PrivateKey privateKey = keyPair.getPrivate();
        // 将公钥和私钥写到对应的文件中
        ObjectOutputStream publicStream = null;
        ObjectOutputStream privateStream = null;
        try {
            log.info("生成公钥和私钥，并写入到对应的文件中");
            File file = new File(PUBLIC_KEY_FILE);
            if (file.exists()) {
                // 公钥已经生成过，不需要重复生成
                log.info("公钥和私钥已经生成，不用重复生成，path: {}", PUBLIC_KEY_FILE);
                return;
            }
            log.info("PUBLIC_KEY_FILE 写入：{}", PUBLIC_KEY_FILE);
            publicStream = new ObjectOutputStream(new FileOutputStream(PUBLIC_KEY_FILE));
            log.info("PRIVATE_KEY_FILE 写入：{}", PRIVATE_KEY_FILE);
            privateStream = new ObjectOutputStream(new FileOutputStream(PRIVATE_KEY_FILE));

            publicStream.writeObject(publicKey);
            privateStream.writeObject(privateKey);
        } catch (Exception e) {
            throw e;
        } finally {
            IOUtil.closeQuietly(publicStream);
            IOUtil.closeQuietly(privateStream);
        }
    }
}
